<?php declare(strict_types=1);

namespace Shopware\Administration\Framework\Twig;

use Pentatrion\ViteBundle\Service\FileAccessor;
use Shopware\Core\Framework\Bundle as ShopwareBundle;
use Shopware\Core\Framework\Log\Package;
use Symfony\Component\Asset\Package as AssetPackage;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;

#[Package('framework')]
class ViteFileAccessorDecorator extends FileAccessor
{
    private string $assetPath;

    /**
     * @var array<string, array<string, array<string, mixed>>>
     */
    private array $content = [];

    /**
     * @internal
     */
    public function __construct(
        array $configs,
        private readonly AssetPackage $package,
        private readonly KernelInterface $kernel,
        private readonly Filesystem $filesystem,
    ) {
        $this->assetPath = $this->package->getUrl('');

        parent::__construct($this->assetPath, $configs, null);
    }

    public function hasFile(string $configName, string $fileType): bool
    {
        try {
            $bundle = $this->getBundleForConfig($configName);
        } catch (\InvalidArgumentException) {
            // we can't find a bundle with that name
            return false;
        }

        return $this->filesystem->exists($bundle->getPath() . $this->getRelativeFileLocation($fileType));
    }

    /**
     * @return array<string, mixed>
     */
    public function getData(string $configName, string $fileType): array
    {
        $bundle = $this->getBundleForConfig($configName);
        // plain symfony bundles don't bring JS assets
        if (!$bundle instanceof ShopwareBundle) {
            return [];
        }

        return $this->getContent($fileType, $bundle);
    }

    /**
     * @return array<string, mixed>
     */
    public function getBundleData(ShopwareBundle $bundle): array
    {
        return $this->getContent(self::ENTRYPOINTS, $bundle);
    }

    private function getRelativeFileLocation(string $fileType): string
    {
        return '/Resources/public/administration/.vite/' . self::FILES[$fileType];
    }

    private function getBundleForConfig(string $configName): BundleInterface
    {
        if ($configName === '_default') {
            $configName = 'Administration';
        }

        return $this->kernel->getBundle($configName);
    }

    private function getTechnicalBundleName(ShopwareBundle $bundle): string
    {
        return str_replace('_', '-', $bundle->getContainerPrefix());
    }

    /**
     * @return array<string, mixed>
     */
    private function getContent(string $fileType, ShopwareBundle $bundle): array
    {
        // Depending on how many script tags are rendered, this method is called multiple times
        // Cache the content to avoid reading the file multiple times
        if (!isset($this->content[$bundle->getName()][$fileType])) {
            $viteEntryPointsPath = $bundle->getPath() . $this->getRelativeFileLocation($fileType);

            if (!$this->filesystem->exists($viteEntryPointsPath)) {
                return $this->content[$bundle->getName()][$fileType] = [];
            }

            $content = json_decode(
                $this->filesystem->readFile($viteEntryPointsPath),
                true,
                flags: \JSON_THROW_ON_ERROR
            );

            // Replace the base generated by Vite with the symfony asset path
            $content['base'] = $this->assetPath;

            $technicalBundleName = $this->getTechnicalBundleName($bundle);

            // Get all entrypoints for the administration
            foreach ($content['entryPoints'][$technicalBundleName] ?? [] as $key => $entrypoint) {
                // The entry points also contain configuration, for example "legacy" and a boolean value
                if (!\is_array($entrypoint)) {
                    continue;
                }

                // Prepend the asset path to the every entry point
                foreach ($entrypoint as $index => $entry) {
                    // There is an edge case where symfony removes the "bundle" suffix from the bundle name
                    // @see \Shopware\Core\Framework\Plugin\Util\AssetService::getTargetDirectory
                    $entry = str_replace('bundle/administration', '/administration', $entry);

                    $content['entryPoints'][$technicalBundleName][$key][$index] = \sprintf('%s%s', $this->assetPath, $entry);
                }
            }

            $this->content[$bundle->getName()][$fileType] = $content;
        }

        return $this->content[$bundle->getName()][$fileType];
    }
}
